/*
author:杨文超
email:yangwenchao@keynection.cn
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/uart.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "geminai_log.h"
#include "sdkconfig.h"
#include "driver/gpio.h"
#include "pthread.h"
#include "esp_vfs.h"
#include "esp_vfs_dev.h"
#include "geminai_rs485.h"

typedef struct rs485_message
{
    struct rs485_message *next;
    char data[127];
}rs485_message;

typedef struct rs485_send_list
{
    rs485_message *first;
    rs485_message *tail;
    int num;
    pthread_mutex_t mutex;
    pthread_cond_t available_cond; //接收队列中有存储RS485消息空间
    pthread_cond_t ready_cond; //接收队列中有可用的消息
}rs485_send_list;

typedef struct rs485_receive_list
{
    rs485_message *first;
    rs485_message *tail;
    int num;
    pthread_mutex_t mutex;
    pthread_cond_t available_cond; //发送队列中有存储RS485消息空间
    pthread_cond_t ready_cond; //发送队列中有可用的消息
}rs485_receive_list;

rs485_send_list send_list = {
    NULL,NULL,0,PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,PTHREAD_COND_INITIALIZER
};

rs485_receive_list receive_list = {
    NULL,NULL,0,PTHREAD_MUTEX_INITIALIZER,PTHREAD_COND_INITIALIZER,PTHREAD_COND_INITIALIZER
};

static const char *TAG = "GEMINAI_RS485";

#define GEMINAI_RS485_TXD   GPIO_NUM_17
#define GEMINAI_RS485_RXD   GPIO_NUM_18

// RTS for RS485 Half-Duplex Mode manages DE/~RE
#define GEMINAI_RS485_RTS   GPIO_NUM_16

// CTS is not used in RS485 Half-Duplex Mode
#define GEMINAI_RS485_CTS   (UART_PIN_NO_CHANGE)


#define BUF_SIZE        (127)
#define BAUD_RATE       (115200)

#define RS485_PACKET_READ_TICS        (100 / portTICK_PERIOD_MS)
#define GEMINAI_RS485_TASK_STACK_SIZE    (4096)
#define GEMINAI_RS485_TASK_PRIO          (10)
#define GEMINAI_RS485_UART_PORT          (1)

// Timeout threshold for UART = number of symbols (~10 tics) with unchanged state on receive pin
#define GEMINAI_RS485_READ_TOUT          (3) // 3.5T * 8 = 28 ticks, TOUT=3 -> ~24..33 ticks

int rs485_fd = -1;

static void receive_list_add(rs485_message *message)
{
    pthread_mutex_lock(&receive_list.mutex);
    if(receive_list.num>=100)
    {
        GEMINAI_LOGI(TAG, "receive list already full. will wait for receive list be consumed");
        pthread_cond_wait(&receive_list.available_cond,&receive_list.mutex);
    }
    if(receive_list.first == NULL)
    {
        receive_list.first = message;
        receive_list.tail = message;
        message->next = NULL;
        receive_list.num++;
        pthread_mutex_unlock(&receive_list.mutex);
        pthread_cond_broadcast(&receive_list.ready_cond);
    }
    else
    {
        rs485_message *prev = receive_list.tail;        
        prev->next = message;
        receive_list.tail = message;
        message->next=NULL;
        receive_list.num++;
        pthread_mutex_unlock(&receive_list.mutex);
    }
}


static void send_list_add(rs485_message *message)
{
    pthread_mutex_lock(&send_list.mutex);
    if(send_list.num>=100)
    {
        pthread_cond_wait(&send_list.available_cond,&send_list.mutex);
    }
    if(send_list.first == NULL)
    {
        send_list.first = message;
        send_list.tail = message;
        message->next = NULL;
        send_list.num++;
        pthread_mutex_unlock(&send_list.mutex);
        pthread_cond_broadcast(&send_list.ready_cond);
    }
    else
    {
        rs485_message *prev = receive_list.tail;        
        prev->next = message;
        receive_list.tail = message;
        message->next=NULL;
        receive_list.num++;
        pthread_mutex_unlock(&receive_list.mutex);
    }

}

static void receive_list_get(rs485_message **message)
{
    pthread_mutex_lock(&receive_list.mutex);

    if(receive_list.num==0)
    {
        pthread_cond_wait(&receive_list.ready_cond,&receive_list.mutex);
    }

    if(receive_list.first == receive_list.tail)
    {
        (*message) = receive_list.first;
        (*message)->next=NULL;
        receive_list.first = NULL;
        receive_list.tail = NULL;
        receive_list.num--;
        pthread_mutex_unlock(&receive_list.mutex);
    }
    else
    {
        rs485_message *next = receive_list.first->next;
        (*message) = receive_list.first;
        (*message)->next=NULL;
        receive_list.first = next;        
        if(receive_list.num == 100)
        {
            receive_list.num--;
            GEMINAI_LOGI(TAG, "receive list now have available space.");
            pthread_mutex_unlock(&receive_list.mutex);
            pthread_cond_broadcast(&receive_list.available_cond);
        }
        else
        {
            receive_list.num--;
            pthread_mutex_unlock(&receive_list.mutex);           
        }
    }

}

static void send_list_get(rs485_message **message)
{
    pthread_mutex_lock(&send_list.mutex);
    if(send_list.num==0)
    {
        pthread_cond_wait(&send_list.ready_cond,&send_list.mutex);
    }
    if(send_list.first == send_list.tail)
    {
        (*message) = send_list.first;
        (*message)->next=NULL;
        send_list.first = NULL;
        send_list.tail = NULL;
        send_list.num--;
        pthread_mutex_unlock(&send_list.mutex);
    }
    else
    {
        rs485_message *next = send_list.first->next;
        (*message) = send_list.first;
        (*message)->next=NULL;
        send_list.first = next;        
        if(send_list.num == 100)
        {
            send_list.num--;
            pthread_mutex_unlock(&send_list.mutex);
            pthread_cond_broadcast(&send_list.available_cond);
        }
        else
        {
            send_list.num--;
            pthread_mutex_unlock(&send_list.mutex);           
        }
    }
}

static void rs485_select_task(void *arg)
{
    const int uart_num = GEMINAI_RS485_UART_PORT;
    uart_config_t uart_config = {
        .baud_rate = BAUD_RATE,
        .data_bits = UART_DATA_8_BITS,
        .parity = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .rx_flow_ctrl_thresh = 122,
        .source_clk = UART_SCLK_DEFAULT,
    };
    // Set UART log level
    esp_log_level_set(TAG, ESP_LOG_INFO);

    GEMINAI_LOGI(TAG, "Start RS485 application and configure UART.");

    // Install UART driver (we don't need an event queue here)
    // In this example we don't even use a buffer for sending data.
    ESP_ERROR_CHECK(uart_driver_install(uart_num, BUF_SIZE * 2, 0, 0, NULL, 0));

    // Configure UART parameters
    ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));

    GEMINAI_LOGI(TAG, "UART set pins, mode and install driver.");

    // Set UART pins as per KConfig settings
    ESP_ERROR_CHECK(uart_set_pin(uart_num, GEMINAI_RS485_TXD, GEMINAI_RS485_RXD, GEMINAI_RS485_RTS, GEMINAI_RS485_CTS));

    // Set RS485 half duplex mode
    //ESP32-S3 UART 控制器本身不支持半双工通信，因其无法自动控制连接到 RS485 总线驱动 RE/DE 输入的 RTS 管脚。
    //然而，半双工通信能够通过 UART 驱动程序对 RTS 管脚的软件控制来实现，调用 uart_set_mode() 并选择 UART_MODE_RS485_HALF_DUPLEX 模式能够启用这一功能。
    //主机开始向 Tx FIFO 缓冲区写入数据时,UART 驱动程序会自动置位 RTS 管脚（逻辑 1）；
    //最后一位数据传输完成后，驱动程序就会取消置位 RTS 管脚（逻辑 0）。要使用此模式，软件必须禁用硬件流控功能。
    ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));

    // Set read timeout of UART TOUT feature
    ESP_ERROR_CHECK(uart_set_rx_timeout(uart_num, GEMINAI_RS485_READ_TOUT));

    GEMINAI_LOGI(TAG, "UART start select loop.\r");    

    while (1) {

        if ((rs485_fd = open("/dev/uart/1", O_RDWR)) == -1) {
            GEMINAI_LOGE(TAG, "Cannot open UART,errno=%d:%s",errno,strerror(errno));
            vTaskDelay(5000 / portTICK_PERIOD_MS);
            continue;
        }


        // We have a driver now installed so set up the read/write functions to use driver also.
        esp_vfs_dev_uart_use_driver(1);

        while (1) {
            int s;
            fd_set rfds;
            struct timeval tv = {
                .tv_sec = 5,
                .tv_usec = 0,
            };

            FD_ZERO(&rfds);
            FD_SET(rs485_fd, &rfds);

            s = select(rs485_fd + 1, &rfds, NULL, NULL, &tv);

            if (s < 0) {
                GEMINAI_LOGE(TAG, "Select failed: errno %d:%s", errno, strerror(errno));
                break;
            } else if (s == 0) {
                GEMINAI_LOGI(TAG, "Timeout has been reached and nothing has been received");
            } else {
                if (FD_ISSET(rs485_fd, &rfds)) {
                    rs485_message *message = malloc(sizeof(rs485_message));
                    memset(message->data,'\0',127);
                    if (read(rs485_fd, &message->data, sizeof(message->data)) > 0) {
                        GEMINAI_LOGI(TAG, "Received: %s", message->data);
                        receive_list_add(message);
                    } else {
                        GEMINAI_LOGE(TAG, "UART read error");
                        break;
                    }
                } else {
                    GEMINAI_LOGE(TAG, "No FD has been set in select()");
                    break;
                }
            }
        }
        close(rs485_fd);

    }
    vTaskDelete(NULL);
}

static void rs485_send_task(void *arg)
{
    while(1)
    {
        rs485_message *message=NULL;
        send_list_get(&message);
        int write_bytes = write(rs485_fd, message->data, strlen(message->data));
        if (write_bytes < 0) {
            GEMINAI_LOGE(TAG, "rs485 write error");
        }
        free(message);
        message=NULL;
    }
}

void rs485_task(void)
{
    xTaskCreate(rs485_select_task, "rs485_select_task", GEMINAI_RS485_TASK_STACK_SIZE, NULL, GEMINAI_RS485_TASK_PRIO, NULL);
    xTaskCreate(rs485_send_task, "rs485_send_task", GEMINAI_RS485_TASK_STACK_SIZE, NULL, GEMINAI_RS485_TASK_PRIO, NULL);
}

void show_receive_list(void)
{
    pthread_mutex_lock(&receive_list.mutex);
    GEMINAI_LOGI(TAG, "***************rs485 receive list*****************");
    GEMINAI_LOGI(TAG, "receive list num =%d", receive_list.num);
    if(receive_list.num > 0)
    {
        int index=1;
        rs485_message *message;
        message = receive_list.first;
        while(message!=NULL)
        {
            GEMINAI_LOGI(TAG, "index %d message = %s", index, message->data);
            message=message->next;
            index++;
        }
    }
    GEMINAI_LOGI(TAG, "**************************************************");
}

void show_send_list(void)
{
    pthread_mutex_lock(&send_list.mutex);
    GEMINAI_LOGI(TAG, "***************rs485 send list*****************");
    GEMINAI_LOGI(TAG, "send list num =%d", send_list.num);
    if(send_list.num > 0)
    {
        int index = 1;
        rs485_message *message;
        message = send_list.first;
        while(message!=NULL)
        {
            GEMINAI_LOGI(TAG, "index %d message = %s", index, message->data);
            message=message->next;
            index++;
        }
    }
    GEMINAI_LOGI(TAG, "**************************************************");
}

void rs485_send_func(void *data)
{
    if(strlen(data)>126)
    {
        GEMINAI_LOGE(TAG, "rs485 transmition data length most 126 byte one time");
        return;
    }
    rs485_message *message=malloc(sizeof(rs485_message));
    memset(message,'\0',sizeof(rs485_message));
    strncpy(message->data, data, strlen(data));  
    send_list_add(message);
}

void rs485_receive_func(void *data)
{
    rs485_message *message=NULL;
    receive_list_get(&message);
    //strncpy(data, message->data, strlen(message->data));
    strncpy(data, message->data, 127);
    free(message);
    message=NULL;
}